# OOP高级编程 本章高级编程中所谓的高级,指的是相对比较抽象,也比较实用的关于类的技术,但对于非强编程的专业,比如数据分析,办公弄自动化等, 可能暂时并不需要, 所以对这一部分同学暂时不推荐学习, 可能您根本不需要,所以我们统一放到了高级编程中. ## 类属性(propety) 类的属性值(attribute)经常需要被方位, 通常的访问为`get`和`set`两个, 即对属性进行复制和读取属性. 如下面案例,我们对属性 `name`的访问不想让外部直接访问, 这涉及对内容的封装,此时可以通过两个函数`setName`和 `getName`来完成, 这就避免了但对属性的直接访问: # 属性案例 # 创建Student类,描述学生类 # 学生具有Student.name属性 # 但name格式并不统一 # 可以用增加一个函数,然后自动调用的方式,但很蠢 class Student(): def __init__(self, name, age): self.name = name self.age = age # 如果不想修改代码 self.setName(name) # 介绍下自己 def intro(self): print("Hai, my name is {0}".format(self.name)) def setName(self, name): self.name = name.upper() s1 = Student("LIU Ying", 19.8) s2 = Student("michi stangle", 24.0) s1.intro() s2.intro() 访问结果如下: Hai, my name is LIU YING Hai, my name is MICHI STANGLE 此时虽然我们给出了避免类的属性直接被访问的方式, 即通过函数访问,但并不能避免属性真的被外部访问,至少语法上不能避免,比如一样可以 直接通过: print(s1.name) 来访问属性. 为了避免此类情况发生,我们可以对属性进行改名,比如添加一个下划线`_`来表明这个属性不希望被外部直接访问,但此种方式只是一个标记,或者叫 约定俗成,如果我访问, 还是没有语法问题的. ### 类属性值(property) 主要为了满足以下需求,我们设计了类的属性(property)这个功能: - 封装类的属性值(attribute), 不许外部访问此类内容 - 对类的属性(attribute)赋值的时候, 可能我们有额外的需求,例如一些额外的操作 此时我们可以用一个函数来代替属性值(attribute) 对一个变量的操作,一般具有赋值,一般也就赋值,读取,删除三个操作,所以使用属性(properety)需要的大致步骤是: 1. 定义三个函数,即对这个属性(property)进行赋值,读取, 删除时候的操作功能 2. 把这三个函数绑定到一个变量名称上, 此后对这个变量名称的访问即调用对于的函数. 具体案例参看下面, 案例中,如果访问`Person`的`name`和`age`属性, 我们希望带有额外的操作, 比如自动进行一些大小写转换 等, 此时可以把`name`等设置成`property`来进行操作: # peroperty案例 # 定义一个Person类,具有name,age属性 # 对于任意输入的姓名,我们希望都用大写方式保存 # 年龄,我们希望内部统一用整数保存 # x = property(fget, fset, fdel, doc) class Person(): ''' 这是一个人,一个高尚的人,一个脱离了低级趣味的人 还他妈的有属性 ''' # 函数的名称可以任意 # 对于此propery读取时候的操作,此时对任意名称的读取, 返回这个名称的两次重复值 def fget(self): return self._name * 2 # 对于对name进行赋值时候的操作, 即默认把值转换成大写 def fset(self, name): # 所有输入的姓名以大写形式保存 self._name = name.upper() # 对于删除时候操作, 删除的时候并不是真正删除,而是把值变成了NoName字符串 def fdel(self): self._name = "NoName" # 调用property函数, 把变量 _name 和三个操作函数绑定到属性 name上, # 以后对name操作即调用相应三个操作函数 name = property(fget, fset, fdel, "对name进行下下操作啦") # 作业: # 1. 在用户输入年龄的时候,可以输入整数,小数,浮点数 # 2. 但内部为了数据清洁,我们统一需要保存整数,直接舍去小数点 # 调用类 p1 = Person() p1.name = "TuLing" # 赋值, 调用fset print(p1.name) # 读取,调用fget 运行结果如下所示: TULINGTULING ### `@property` 对上述属性系统还给提供了装饰器来完成相应的工作 单独对某个函数进行修饰后, 则此函数名称就是一个具备只读功能的属性(property), 注意此属性 只能读取,不能有别的功能, 例如下面案例,如果对属性`year`进行除了读取以外操作, 报错! 代码如下, 代码中只用`property`修饰, 则属性只具有只读性质: class Person(): def __init__(self): self._age = 18 @property def name(self): return self._name @name.setter def name(self, name): self._name = "北京图灵学院" + name @property def year(self): return 2022 - self._age p = Person( ) # 读取year属性 print(p.year) # year为只读属性, 尝试其他操作报错 p.year = 1997 执行上述代码后, 报错如下: Connected to pydev debugger (build 211.7142.13) 2004 Traceback (most recent call last): File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec pydev_imports.execfile(file, globals, locals) # execute the script File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile exec(compile(contents+"\n", file, 'exec'), glob, loc) File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 26, in p.year = 1997 AttributeError: can't set attribute python-BaseException 如果想让定义的属性`property`具有其他能力,比如赋值删除等, 则需要继续时候用装饰器来完成,参考 下面代码,定义的属性`name`具有删除,赋值,读取功能: class Person(): def __init__(self): self._name = "NoName" @property def name(self): return self._name @name.setter def name(self, n): self._name = n.upper() @name.deleter def name(self): self._name = "IsDeleted" p = Person( ) # 读取name属性 print(p.name) # 调用setter p.name = "dana liu" print(p.name) # 调用deleter del p.name # 调用getter print(p.name) 代码运行如下: /Users/bbb/anaconda3/bin/python3 /Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py --multiproc --qt-support=auto --client 127.0.0.1 --port 62518 --file /Users/bbb/baoshu/book/python/new_python/code/tt.py Connected to pydev debugger (build 211.7142.13) NoName DANA LIU IsDeleted 从上面案例也可以基本推断出, 所谓装饰器就是对上一小节中属性函数的简单包装, 在使用中也是 对赋值,读取,删除三个函数调用而已. > 属性(property)的命名务必跟类中使用的属性(attribute)不能相同,否则会造成递归调用,自己想想点解? ## 类的内置属性 类会内置了很多属性,通过这些属性方便我们对类的使用和控制, 一般此类内置都以双下划线开头结尾, 我们介绍四个内置属性如下: - `__dict__`: 类或者实例的所有属性(是所有,包括attribute, 函数, property等) - `__doc__`: 类的文档 - `__name__`: 类的名称 - `__bases__`: 类的父类 上面知识点以类`Person`为例, 类的实现代码如下: class Animal(): pass class Person(Animal): ''' 这是一个人,一个高尚的人,一个脱离了低级趣味的人 还他妈的有属性 ''' # 函数的名称可以任意 # 对于此propery读取时候的操作,此时对任意名称的读取, 返回这个名称的两次重复值 def fget(self): return self._name * 2 # 对于对name进行赋值时候的操作, 即默认把值转换成大写 def fset(self, name): # 所有输入的姓名以大写形式保存 self._name = name.upper() # 对于删除时候操作, 删除的时候并不是真正删除,而是把值变成了NoName字符串 def fdel(self): self._name = "NoName" # 调用property函数, 把变量 _name 和三个操作函数绑定到属性 name上, # 以后对name操作即调用相应三个操作函数 name = property(fget, fset, fdel, "对name进行下下操作啦") def __init__(self): self.p_name = "NoName" self.age = 19 ### `__dict__` 如果查看类或者实例的内容, 则可以直接按下面代码打印: p = Person( ) print(Animal.__dict__) print("=" * 30) print(Person.__dict__ ) print("=" * 30) print( p.__dict__ ) 打印结果如下: Connected to pydev debugger (build 211.7142.13) {'__module__': '__main__', '__dict__': , '__weakref__': , '__doc__': None} ============================== {'__module__': '__main__', '__doc__': '\n 这是一个人,一个高尚的人,一个脱离了低级趣味的人\n 还他妈的有属性\n ', 'fget': , 'fset': , 'fdel': , 'name': , '__init__': } ============================== {'p_name': 'NoName', 'age': 19} > 这里类的属性和实例的属性进行了严格的区分, 这两者内容也大不一样 > 子类和父类也各自维持各自的`__dict__`内容 > 内置类型list,tuple,dict等没有`__dict__`属性 可以简单理解成: - 实例的`__dict__`包含的是类定义的时候前面有`self.`的内容,例如`self.age` - 类的`__dict__`包含的是出去实例`__dict__`之外的所有 ### `__name__` `__name__`可以用来代表一段代码的名称,我们在写代码的时候经常写一个入口程序: if __name__ == "__main__": pass 作为一段段代码的第一行执行程序, 就是要检查这段代码的名称是否是一个值. 在类中我们对类调用`__name__`属性的时候代表的是这个类的名字, 不如下面代码: p = Person( ) print(Animal.__name__) print("=" * 30) print(Person.__name__ ) print("=" * 30) print(__name__) print("=" * 30) print(p.__name__) 代码执行结果如下: Connected to pydev debugger (build 211.7142.13) Animal ============================== Person ============================== __main__ ============================== python-BaseException Traceback (most recent call last): File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec pydev_imports.execfile(file, globals, locals) # execute the script File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile exec(compile(contents+"\n", file, 'exec'), glob, loc) File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 45, in print(p.__name__) AttributeError: 'Person' object has no attribute '__name__' 代码最后一行执行保存, 因为实例变量`p`没有`__name__`属性 ### `__bases__` / `__base__` 用来表示一个类的父类和父类的列表. 参见下面代码: p = Person( ) print(Person.__base__) print(Person.__bases__) 执行结果如下: Connected to pydev debugger (build 211.7142.13) (,) ## 魔法函数 类的魔法函数是指一类特殊的函数: 1. 此类函数由类定义名称, 不可以随意命名 2. 一般在某个时机会被自动调用,不需要手动调用 3. 每个魔法函数都对于一个固定的调用时机, 通常也默认有固定的的功能和写法, 如果实现, 则时机到了会被自动调用 4. 名称由前后双下划线包裹 我们以前介绍的构造函数就是一个魔法函数, 类实例化的时候被调用,具有特定的写法和命名等. 本章我们介绍其他常用的魔法函数. ### `__call__` 实例可以直接被调用, 此时会执行`__call__`函数: class A(): def __init__(self, name = 0): print("哈哈,我被调用了") def __call__(self): print("我被调用了again") 定义后用下面两行代码,第一行生成实例,第二行直接运行实例,此时调用魔法函数: a = A() a() 运行结果如下: 哈哈,我被调用了 我被调用了again ### `__str__` 当实例被需要当做字符串处理的时候会调用此函数, 如果没有实现此函数会执行默认功能. 此函数要求返回一个字符串. class A(): def __init__(self, name = 0): print("哈哈,我被调用了") def __call__(self): print("我被调用了again") def __str__(self): return "图灵学院的例子" a = A() print(a) 在执行`print(a)`的时候, 需要把实例转成一个字符串,此时会执行上面的魔法函数, 上面魔法函数返回的内容 就是打印出来的内容. 哈哈,我被调用了 图灵学院的例子 ### `__geattr__` 调用某个属性的时候, 如果这个属性不存在, 会自动执行这个魔法函数. 魔法函数的返回值作为不存在的这个属性的值. class A(): name = "NoName" age = 18 def __getattr__(self, name): print("没找到呀没找到") print(name) 定义完后执行下面代码: a = A() print(a.name) print(a.addr) # 作业: # 为什么会打印第四句话,而且第四句话是打印的 None # 答案在QQ群9990960 执行结果如下: NoName 没找到呀没找到 addr None ### `__setattr__` 当给类的属性进行赋值的时候, 自动调用此函数. > 需要注意此函数的写法,比较特殊 > 魔法函数的很多写法都比较特殊,性质决定 参考案例: class Person(): def __init__(self): pass def __setattr__(self, name, value): print("设置属性: {0}".format(name)) # 下面语句会导致问题,死循环 #self.name = value # 此种情况,为了避免死循环,规定统一调用父类魔法函数 super().__setattr__(name, value) 调用上面定义的类: p = Person() print(p.__dict__) p.age = 18 print(p.__dict__) 结果如下: Connected to pydev debugger (build 211.7142.13) {} 设置属性: age {'age': 18} ### `__gt__` 此类魔法函数在比较两个类实例的时候会被自动调用,除了大于,小于,还有大于等于,小于等于, 等于,不等于等. 参看下面代码, 如果比较两个学生类的实例, 则以魔法函数返回的结果为比较的结果: class Student(): def __init__(self, name): self._name = name # 比较两个实例按名称比较 def __gt__(self, obj): print("哈哈, {0} 会比 {1} 大吗?".format(self, obj)) return self._name > obj._name 调用代码如下: # 作业: # 字符串的比较是按什么规则 stu1 = Student("one") stu2 = Student("two") print(stu1 > stu2) 执行结果如下: # 作业: # 下面显示结果不太美观,能否改成形如 "哈哈, one 会比 two 大吗?“ ``` 哈哈, <__main__.Student object at 0x7f4aac6b3b00> 会比 <__main__.Student object at 0x7f4aac6b3ac8> 大吗? False ## 抽象类 有些类不需要定义实例, 这些类存在的意义就是被继承, 这些类我们可以定义成抽象类,即只需要被继承不能够 被实例化的类. 1. 定义抽象类需要借助`abc`模块, 即抽象类是`abc.ABCMeta`的子类. 2. 抽象类包含至少一个抽象函数, 此时需要用到装饰器`@abc.abstractxxxxx`来定义一个抽象函数, 此类 函数不需要有实现代码, 只能被继承, 然后在子类中实现具体功能 3. 一个包含抽象函数的类就是抽象类, 哪怕只包含一个抽象函数, 则这个类就是抽象的,不能被实例化 4. `@abc.abstractxxxx`装饰器只是说你这个函数是的性质,属于抽象的且是xxxx函数,包括 - `abstractmethode`: 普通抽象函数 - `abstractclassmethode`:抽象类函数 - `abstractstaticmethode`: 抽象静态函数 - `abstractproperty`: 抽象属性 参看代码,从一个了一个人类, 因为人类具有抽象函数(还不止一个), 所以是一个抽象类 # 抽象类的实现 import abc #声明一个类并且指定当前类的元类 class Human(metaclass=abc.ABCMeta): # 定义一个抽象的方法 @abc.abstractmethod def smoking(self): pass # 定义类抽象方法 @abc.abstractclassmethod def drink(): pass # 定义静态抽象方法 @abc.abstractstaticmethod def play(): pass def sleep(self): print("Sleeping.......") 此时直接用`Human`实例化是会报错的: Connected to pydev debugger (build 211.7142.13) Traceback (most recent call last): File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec pydev_imports.execfile(file, globals, locals) # execute the script File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile exec(compile(contents+"\n", file, 'exec'), glob, loc) File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 22, in h = Human() TypeError: Can't instantiate abstract class Human with abstract methods drink, play, smoking python-BaseException ## 元类 ### 手动组装类 函数可以当做换一个变量处理,此时函数可以当做值来传递,这是我们在函数中已经给大家讲过的内容, 在类中定义的函数,包括 实例函数,类函数, 静态函数等,都可以看做一个变量来处理,即我们可以灵活的组装一个类. 参看下面代码, 我们定义了一个类`A`, 但没有具体内容, 同时定义了函数`say`, 这就是一个普通函数,我们可以单独调用, 也可以把这个函数作为值传递给`A`中的某一个变量, 此时可以把函数`say`作为类的函数调用,同时在`A`的实例调用的时候, 第一个参数会自动把自己传入, 此时如果想把`say`当做实例函数,必须至少有一个参数,否则报错: class A(): pass def say(self): print("Saying... ...") class B(): def say(self): print("Saying......") # 单独调用say'必须有对参数,否则报错 say(9) # 给类A添加一个实例函数say A.say = say a = A() a.say() # 打印出A的内容 print(A.__dict__) 上述代码运行结果如下: onnected to pydev debugger (build 211.7142.13) Saying... ... Saying... ... {'__module__': '__main__', '__dict__': , '__weakref__': , '__doc__': None, 'say': } 可以看出, 我们通过后期的赋值,可以随意赋予一个类以函数 这类赋值不可以直接对类实例进行,例如下面代码: class A(): pass def say(self): print("Saying... ...") a1 = A() a2 = A() a1.say = say # 此时say作为a`是一个普通函数, 不是类中定义的 # 调用可以这样 a1.say(a1) # 但如果是按照实例方法调用则报错 a1.say() 运行结果如下: Connected to pydev debugger (build 211.7142.13) Saying... ... Traceback (most recent call last): File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec pydev_imports.execfile(file, globals, locals) # execute the script File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile exec(compile(contents+"\n", file, 'exec'), glob, loc) File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 19, in a1.say() TypeError: say() missing 1 required positional argument: 'self' python-BaseException ### `MethodType` 组装类 `MethodType`方法可以给实例对象或类绑定方法, 功能同上面全手动类似. 下面代码给实例利用`MethodType`增加函数功能, 但此时并没有真正修改类`A`的内容, 所以对实例`a2`修改后 面的代码`a1,a3`再次调用`say`就会报错. # 自己组装一个类 from types import MethodType class A(): pass class B(): pass def say(self): print("Saying... ...") a1 = A() a2 = A() a2.say = MethodType(say, A) a2.say() a3 = A() # 以下使用都会报错, 报错信息为A没有say属性 a3.say() a1.say() 代码运行后结果如下: Connected to pydev debugger (build 211.7142.13) Saying... ... python-BaseException Traceback (most recent call last): File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1483, in _exec pydev_imports.execfile(file, globals, locals) # execute the script File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile exec(compile(contents+"\n", file, 'exec'), glob, loc) File "/Users/bbb/baoshu/book/python/new_python/code/tt.py", line 24, in a3.say() AttributeError: 'A' object has no attribute 'say'Vk 如果想直接修改类的定义内容,则需要直接给类赋值,代码如下: # 自己组装一个类 from types import MethodType class A(): pass def say(self): print("Saying... ...") A.say = MethodType(say, A) a1 = A() a2 = A() a3 = A() a1.say() a2.say() a3.say() 上面代码修改了类的内容,以后由这个类产生的实例都会具备相应功能, 运行结果如下: Connected to pydev debugger (build 211.7142.13) Saying... ... Saying... ... Saying... ... ### 利用`type`来创造类 `python`中只有`type`才是唯一真神, 他可以创建万物,包括类. 我们可以利用这来完全定义类, 参看下代码: # 先定义类应该具有的成员函数 def say(self): print("Saying.....") def talk(self): print("Talking .....") # 用type来创建一个类 # thype的参数含义, 1,类名称, 2.类的父类们, 3, 类的内容, 字典形式 A = type("AName", (object,), {"class_say": say, "class_talk": talk}) # 然后可以像正常访问一样使用类 a = A() a.class_say() a.class_talk() print(a.class_say) print(a.class_talk) 上例中,我们用`type`无中生有了一个类, 同时用这个类创建实例并运行, 味道好极了, 运行结果如下: Connected to pydev debugger (build 211.7142.13) Saying..... Talking ..... > > ### 元类 元类是负责创建类的类, 叫做元类. 我们上面使用各种方法, 理论上也可以`创建`一个类,但元类是专业负责创建类的一个有着特殊写法的类. 元类写法固定: 1. 必须继承自`type` 2. 一般以`MetaClass`结尾 3. 实现内容和步骤有规定,要实现`__new__`魔法函数 > 其实元类就是一个批量创建类的模板 参看下面代码, 我们实现一个元类, 负责创建其余的类. # 元类演示 # 元类写法是固定的,必须继承自type # 元类一般命名以MetaClass结尾 class TulingMetaClass(type): # 注意以下写法 def __new__(cls, name, bases, attrs): # 自己的业务处理 print("哈哈,我是元类呀") attrs['id'] = '000000' attrs['addr'] = "北京海淀区公主坟西翠路12号" attrs['douyin'] = "liudana" attrs['wechat'] = "131 191 44 223" # 固定返回 return type.__new__(cls, name, bases, attrs) # 元类定义完就可以使用,使用注意写法 # 必须表明metaclass属性, 即指明利用哪个元类创建这个类 class Teacher(object, metaclass=TulingMetaClass): pass t = Teacher() print(t.id) print(t.douyin) print(t.wechat) 上面代码运行解雇如下: Connected to pydev debugger (build 211.7142.13) 哈哈,我是元类呀 000000 liudana 131 191 44 223 ## 迷信(Mixin) `Mixin`这种设计模式表达的意思是掺入. `python`类的血统比较乱,不拒绝多继承,这就导致一些继承方面的问题(菱形继承), 一个人爸爸太多了总有种说不出的感觉, 代码也是, 让人混乱,为了解决此类问题, 我们不在推荐退继承, 但需要功能拓展怎么办? 我们用迷信(Mixin)处理. 迷信的宗旨就是把父类和子类的耦合关系降到最低,甚至是零, 迷信本质上还是父类,但这个父类本质上只负责处理业务, 完全做到了跟子类除了 业务外代码没任何耦合度(或者尽量减少耦合度). `Mixin`的使用原则是: - 首先他必须表示某一单一功能,而不是某个物品 - 职责必须单一,如果由多个功能,则写多个`Mixin` - `Mixin`不能依赖于子类的实现 - 子类即使没有继承这个`Mixin`类, 也能照样工作,只是缺少了某个功能 - Mixin类中不应该显式的出现__init__初始化方法 - Mixin类通常不能独立工作,因为它是准备混入别的类中的部分功能实现 - Mixin类的祖先类也应是Mixin类 - 使用时,Mixin类通常在继承列表的第一个位置 - 父类只做方法的定义 - 抽象类,抽象方法 - 其他语言抽象类不可实例化 - 抽象类一般不实例化,作为基类用 使用`Mixin`的有点: - 使用Mixin可以在不对类进行任何修改的情况下,扩充功能 - 可以方便的组织和维护不同功能组件的划分 - 可以根据需要任意调整功能类的组合 - 可以避免创建很多新的类,导致类的继承混乱 下面代码中所谓的迷信完全就是父类,只是跟子类或者其他内容的耦合度很低: # MiuIn案例 class Person(object): pass class FWordMixin(): def fxxk(self): print("这特么地........") class HappyMixin(): def happy(self): print("娃哈哈哈哈哈哈........") class TeacherWithNothing(Person): ''' 一个冰冷的莫得感情的老师 ''' name = "别的老师" def work(self): print("上课啦.....") class TeacherWithHappy(Person, HappyMixin): ''' 一个快乐的老师 ''' name = "别的老师" def work(self): print("上课啦.....") class LiuDana(Person, FWordMixin): ''' 大拿上课老骂人, 这不好,阿弥陀佛 ''' name = "别的老师" def work(self): print("上课啦.....") nothing = TeacherWithNothing() nothing.work() happy = TeacherWithHappy() happy.work() happy.happy() dana = LiuDana() dana.fxxk() 运行结果如下: Connected to pydev debugger (build 211.7142.13) 上课啦..... 上课啦..... 娃哈哈哈哈哈哈........ 这特么地........ 上面代码可以看出, 老师类的高兴和骂人功能,只是一个迷信,如果需要,只是简单的继承自这个迷信即可. > 迷信就是特殊的父类,且一般不把他看做父类,只看着功能的横向扩展 ## 类相关函数 在对类的使用中, 我们可能需要对类进行判断甚至控制,此时我们可以借助于下面的一些函数进行. - `issubclass`:检测一个类是否是另一个类的子类 - `isinstance`:检测一个对象是否是一个类的实例 - `hasattr`:检测一个对象是否由成员xxx - `getattr`: get attribute - `setattr`: set attribute - `delattr`: delete attribute - `dir`: 获取对象的成员列表 代码简单,参看下面案例即可: class A(): pass class B(A): def __init__(self): self.name = "刘大拿" self.tel = '131 191 44223' self.qq_group = "9990960" def say(self): print("Hello ......") # 第一个参数是判断的类名, 第二个参数是可能的父类组成的tuple` print("B是A的子类: ", issubclass(B, (A, ))) b = B() print('b是B的实例:', isinstance(b, B)) # 第二个参数字符串形式的属性名称 print("B是否具有属性say:", hasattr(b, "say")) # 利用字符串得到某个具体的函数或者功能 # 这个在系统级别的代码中常用 say = getattr(b, "say") # 注意这个调动方式, say必须要求有一个参数,但此时这个函数代表的是b实例的函数say,此种方式调用照样默认传入b作为第一个参数 say() # 给实例或者类设置内容 b = B() # 给实例b设置一个属性age setattr(b, "age", 18) print(b.age) def sayage(self, age): print("AGE.......", age) # 同样可以把函数设置给b,但此时这个函数不作为b的实例函数, 调用的时候也不默认把自身当做第一个参数传入 setattr(b, "sayage", sayage) b.sayage(b, 23) # 显示类,实例所有可用内容,属性 print(dir(b)) print(dir(B))